package cn.funeralobjects.youtube.dl.extension.model;

import cn.funeralobjects.youtube.dl.extension.utils.CommonUtils;
import lombok.Data;
import lombok.experimental.Accessors;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Writer;
import java.util.concurrent.ExecutorService;
import java.util.function.Consumer;
import java.util.regex.Pattern;

/**
 * @author FuneralObjects
 * Create date: 2020/4/3 4:46 PM
 */
@Data
@Accessors(chain = true)
public class Running {
    private static final Pattern DOWNLOAD_REGX_PATTERN = Pattern.compile("\\[download]\\s*[0-9]*\\.?[0-9]*%\\s*of\\s*[0-9]*\\.?[0-9]*.*\\s*at\\s*[0-9]*\\.?[0-9]*.*\\s*ETA.*");
    private static final String ERROR_HEADER = "----------- ERROR -----------";
    private static final String WARNING_PREFIX = "WARNING:";
    private String now;
    private BufferedReader reader;
    private BufferedReader errorReader;
    private StringBuilder error;
    private Process process;
    private Consumer<Boolean> callback;
    private Writer logWriter;
    private Boolean stopped;

    public Running(Process process, Writer logWriter) {
        this.reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
        this.errorReader = new BufferedReader(new InputStreamReader(process.getErrorStream()));
        this.process = process;
        this.stopped = false;
        this.error = new StringBuilder(ERROR_HEADER).append(CommonUtils.newLine());
        this.logWriter = logWriter;
    }

    public void execute(ExecutorService executor){
        executor.execute( () -> {
            boolean isDownloading = false;
            boolean preIsDownloading;
            String pre = null;
            long flushTime = System.currentTimeMillis();
            try {
                int index = 0;
                while (!this.stopped && (this.now = this.reader.readLine()) != null) {
                    if (System.currentTimeMillis() - flushTime > 10000) {
                        index = 0;
                        flushTime = System.currentTimeMillis();
                        this.logWriter.flush();
                    }
                    preIsDownloading = isDownloading;
                    isDownloading = DOWNLOAD_REGX_PATTERN.matcher(this.now).matches();
                    if (isDownloading && preIsDownloading) {
                        continue;
                    }
                    if (isDownloading && pre != null) {
                        this.writeLog(pre);
                    }
                    index++;
                    this.writeNewLineLog(this.now);
                    pre = this.now;
                    if (index >= 10) {
                        index = 0;
                        flushTime = System.currentTimeMillis();
                        this.logWriter.flush();
                    }
                }
                boolean hasError = false;
                while (!this.stopped && (this.now = this.errorReader.readLine()) != null) {
                    if (this.now.startsWith(WARNING_PREFIX)) {
                        this.writeNewLineLog(this.now);
                        continue;
                    }
                    error.append(this.now).append(CommonUtils.newLine());
                    hasError = true;
                }

                if(hasError){
                    this.writeLog(this.error.toString());
                    callback.accept(false);
                    return;
                }
                callback.accept(!this.stopped);
            } catch (IOException e) {
                callback.accept(false);
                throw new RuntimeException(e);
            }finally {
                this.stop();
            }
        });
    }

    public void stop(){
        try {
            this.stopped = true;
            this.reader.close();
            this.errorReader.close();
            this.logWriter.close();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        if (this.process.isAlive()) {
            this.process.destroy();
        }
    }

    private void writeLog(String str) {
        if (!this.stopped) {
            try {
                this.logWriter.write(str);
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    }

    private void writeNewLineLog(String str) {
        this.writeLog(str);
        this.writeLog(CommonUtils.newLine());
    }

}
